﻿using Daemon.Common;
using Daemon.Data.Substructure.Const;
using Daemon.Model;
using Daemon.Repository.Contract;
using Daemon.Service.Contract;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Daemon.Service
{
    public class UserAuthorizationService : IUserAuthorizationService
    {
        private const int DEFAULT_MAX_LOGIN_FAILED_TIMES = 3;
        private readonly IConfiguration _configuration;
        private readonly IAdminUserRepository _adminUserRepository;
        private readonly IUserLogRepository _userLogRepository;

        private readonly IUserRoleRepository _userRoleRepository;

        private readonly IAdminRoleRepository _adminRoleRepository;

        public UserAuthorizationService(IAdminRoleRepository adminRoleRepository, IUserRoleRepository userRoleRepository, IAdminUserRepository adminUserRepository, IUserLogRepository userLogRepository, IConfiguration configuration)
        {
            _configuration = configuration;
            _adminUserRepository = adminUserRepository;
            _userLogRepository = userLogRepository;
            _userRoleRepository = userRoleRepository;
            _adminRoleRepository = adminRoleRepository;
        }

        public int GetMaxLoginFailedTimes()
        {
            var maxLoginFailedTimes = Convert.ToInt32(_configuration[""]?.ToString() ?? "0");
            maxLoginFailedTimes = (maxLoginFailedTimes != 0) ? maxLoginFailedTimes : DEFAULT_MAX_LOGIN_FAILED_TIMES;

            if (maxLoginFailedTimes <= 0)
            {
                maxLoginFailedTimes = DEFAULT_MAX_LOGIN_FAILED_TIMES;
            }

            return maxLoginFailedTimes;
        }

        public AdminUser GetUserEntity(string username, List<int> roleIds = null, string userType = null)
        {
            return CreateUserEntityWithNameAndRoles(username, roleIds, userType);
        }


        public AdminUser GetUserEntity(int userId, List<int> roleIds = null, string userType = null)
        {
            var userName = _adminUserRepository.FindAll().FirstOrDefault(r => r.Id == userId)?.UserName;
            if (string.IsNullOrEmpty(userName))
            {
                return null;
            }

            return GetUserEntity(userName, roleIds, userType);
        }

        public AdminUser GetUserEntity(string username, string password, out string error)
        {
            return UserValidate(username, password, out error);
        }

        public AdminUser UserValidate(string username, string password, out string error)
        {
            error = MessageConst.INVALID_CLIENT_USERNAME_PW;
            var user = _adminUserRepository.FindAll().FirstOrDefault(r => r.UserName == username);
            if (user == null || user.Password == null)
            {
                return null;
            }
            var userLog = _userLogRepository.FindById(user.Id) ?? new UserLog { UserID = user.Id };
            var loginFailedTimes = userLog.NumCount;
            var lastLoginFailed = userLog.LastAttempt;
            var maxLoginFailedTimes = GetMaxLoginFailedTimes();
            var lockoutMsg = $"你的账户已经被锁定因为你3次登录失败，请联系你的管理员！";
            if (lastLoginFailed.Date < DateTime.Now.Date)
            {
                loginFailedTimes = 0;
            }

            if (loginFailedTimes >= maxLoginFailedTimes)
            {
                error = lockoutMsg;
                return null;
            }

            var userWithPassword = GetUserEntity(username, password);
            if (userWithPassword == null)
            {
                loginFailedTimes += 1;
                userLog.NumCount = loginFailedTimes;
                userLog.LastAttempt = DateTime.Now;
                _userLogRepository.AddOrUpdate(userLog);
                if (loginFailedTimes >= maxLoginFailedTimes)
                {
                    error = lockoutMsg;
                }
                else
                {
                    error = $"你已经登录失败{loginFailedTimes}次" + $". 你的账户将被锁定在{maxLoginFailedTimes}次登录失败以后.";
                }

                return null;
            }

            if (userLog.Id > 0)
            {
                _userLogRepository.Delete(userLog);
            }

            error = null;
            return userWithPassword;
        }

        private AdminUser CreateUserEntityWithNameAndRoles(string username, List<int> addRoleIds = null, string userType = null)
        {
            var user = _adminUserRepository.FindAll().FirstOrDefault(r => r.UserName == username);
            if (user != null)
            {
                user.UserType = userType;
            }

            List<UserRole> userRoles = new List<UserRole>();
            userRoles = _userRoleRepository.FindAll().Include(r => r.AdminRole).Where(r => r.UserId == user.Id).ToList();

            List<int> roleIds = new List<int>();
            roleIds = _userRoleRepository.FindAll().Where(r => r.UserId == user.Id).Select(r => r.Id).ToList();
            var existRoleIds = userRoles.Select(r => r.RoleId).ToArray();
            roleIds = roleIds.Except(existRoleIds).ToList();
            if (roleIds.Any())
            {
                var roleData = _adminRoleRepository.FindAll().Where(r => roleIds.Contains(r.Id)).ToList();
                userRoles.AddRange(roleData.Select(r => new UserRole() { RoleId = r.Id, AdminRole = r, UserId = user.Id }));
            }

            var isAdmin = roleIds.Contains(SystemConst.ADMINISTRATOR_ROLE_ID);
            if (!userRoles.Any())
            {
                return user;
            }

            user.UserRoles = userRoles;
            user.Roles = userRoles.Select(r => r.AdminRole).ToList();
            user.IsAdmin = isAdmin || user.IsAdmin;
            return user;
        }

        private AdminUser GetUserEntity(string username, string password)
        {
            var adminUser = _adminUserRepository.FindAll().FirstOrDefault(r => r.UserName == username);
            var userRoles = _userRoleRepository.FindAll().Include("AdminRole").Where(r => r.UserId == adminUser.Id).ToList();
            adminUser.UserRoles = userRoles;
            if (adminUser?.Password != null && string.Equals(PasswordHelper.Rfc2898Decrypt(adminUser?.Password, "daemon"), password))
            {
                adminUser.UserType = "Security";
                return adminUser;
            }

            return null;
        }
    }
}

